home *** CD-ROM | disk | FTP | other *** search
- // WaisQuestion.m
- //
- // Free software created 1 Feb 1992
- // by Paul Burchard <burchard@math.utah.edu>.
- // Incorporating:
- /*
- WIDE AREA INFORMATION SERVER SOFTWARE:
- No guarantees or restrictions. See the readme file for the full standard
- disclaimer.
-
- This is part of the [NeXTstep] user-interface for the WAIS software.
- Do with it as you please.
-
- Version 0.82
- Wed Apr 24 1991
-
- jonathan@Think.COM
-
- */
- //
-
- #import "WaisQuestion.h"
-
- // Search path for questions.
- static id questionFolderList;
-
- // Maximum number of search results (unless instance's searchLimit is higher).
- static int globalSearchLimit = SEARCH_LIMIT_DEFAULT;
-
- // Error panel title.
- static char *errorTitle = "WAIS Question Error!";
-
- // Decoders for structured WAIS files.
-
- _WaisDecoder waisRectDecoder[] =
- {
- { ":left", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { ":right", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { ":top", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { ":bottom", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { NULL }
- };
-
- _WaisDecoder waisQuestionDecoder[] =
- {
- { ":version", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { ":seed-words", W_FIELD,0,0, ReadString,3, WriteString,2,
- MAX_SYMBOL_SIZE },
- { ":relevant-documents",W_LIST,
- ":document-id", waisDocumentIDDecoder },
- { ":sources", W_LIST,
- ":source-id", waisSourceIDDecoder },
- { ":result-documents",W_LIST,
- ":document-id", waisDocumentIDDecoder },
- { ":view", W_FIELD,0,0, ReadLongS,2, WriteLongS,2 },
- { ":window-size", W_STRUCT,
- ":rect", waisRectDecoder },
- { NULL }
- };
-
-
- @implementation WaisQuestion
-
- + folderList
- {
- return questionFolderList;
- }
-
- + setFolderList:aList
- {
- questionFolderList = aList;
- return self;
- }
-
- + (const char *)defaultHomeFolder
- {
- return "/Library/WAIS/questions";
- }
-
- + (const char *)fileStructName
- {
- return ":question";
- }
-
- + (WaisDecoder)fileStructDecoder
- {
- return waisQuestionDecoder;
- }
-
- + (const char *)errorTitle
- {
- return errorTitle;
- }
-
- + (BOOL)checkFileName:(const char *)fileName
- {
- if(!fileName) return NO;
- if(strlen(fileName) <= strlen(W_Q_EXT)) return NO;
- if(!strstr(fileName, W_Q_EXT)) return NO;
- if(0 != strcmp(W_Q_EXT, strstr(fileName, W_Q_EXT))) return NO;
- return YES;
- }
-
- - initKey:(const char *)aKey
- {
- [super initKey:aKey];
- sourceList = [[List alloc] init];
- relevantList = [[List alloc] init];
- resultList = [[List alloc] init];
- scoreList = [[Storage alloc] initCount:0 elementSize:sizeof(float) description:"f"];
- searchLimit = 1;
- listCounter = 0;
- return self;
- }
-
- - free
- {
- [sourceList free];
- [relevantList free];
- [resultList free];
- [scoreList free];
- return [super free];
- }
-
- - (float)scoreForDocument:waisDocument
- {
- int index;
- float *value;
-
- if(![waisDocument isKindOf:[WaisDocument class]]) return 0.0;
- if((index=[resultList indexOf:waisDocument]) == NX_NOT_IN_LIST) return 0.0;
- if(!(value=(float *)[scoreList elementAt:index])) return 0.0;
- return *value;
- }
-
- - setScoresFromResults
- {
- int r, nresult;
- long max_score, this_score;
- float *score_array;
- const char *score;
-
- // Compute and normalize scores.
- nresult = [resultList count];
- [scoreList setNumSlots:nresult];
- score_array = (float *)[scoreList elementAt:0];
- if(!score_array) return nil;
- max_score = 0;
- for(r=0; r<nresult; r++)
- if((score=[[resultList objectAt:r] valueForStringKey:":score"])
- && max_score<(this_score=atol(score)))
- max_score = this_score;
- if(max_score <= 0)
- {
- [scoreList setNumSlots:0];
- ErrorMsg(errorTitle,
- "Found no documents with positive matching scores.");
- return nil;
- }
- for(r=0; r<nresult; r++)
- if(score=[[resultList objectAt:r] valueForStringKey:":score"])
- score_array[r] = ((float)atol(score))/((float)max_score);
- else score_array[r] = 0.0;
- return self;
- }
-
- - search
- {
- int i, s, d, r, nrelevant, nsource, nretrieve, nresult;
- long request_buffer_length, limit, startat, endat;
- id source, doc;
- const char *database, *theType, *score;
- char buf[MAX_SYMBOL_SIZE];
- static char request[MAX_MESSAGE_LEN], response[MAX_MESSAGE_LEN];
- DocObj **Doco;
- DocID *docid;
- SearchResponseAPDU *interp_response;
- WAISDocumentHeader *header;
- diagnosticRecord **diag;
-
- // Start results fresh.
- [resultList empty];
- [scoreList setNumSlots:0];
-
- // The DocObj list Doco formats the relevant documents for the WAIS lib.
- Doco = (DocObj**)s_malloc(([[self relevantList] count]+1)*sizeof(char*));
- nrelevant = [[self relevantList] count];
- for(d=0; d<nrelevant; d++)
- {
- doc = [[self relevantList] objectAt:d];
- if((docid=[doc waisDocID]) && docid->originalLocalID)
- {
- theType = [doc valueForStringKey:":type"];
- if(!theType) theType = "TEXT";
- if([doc valueForStringKey:":start"])
- startat = atol([doc valueForStringKey:":start"]);
- else startat = (-1);
- if([doc valueForStringKey:":end"])
- endat = atol([doc valueForStringKey:":end"]);
- else endat = (-1);
- if(startat >= 0) Doco[d] = makeDocObjUsingLines(
- anyFromDocID(docid), theType, startat, endat);
- else Doco[d] = makeDocObjUsingWholeDocument(
- anyFromDocID(docid), theType);
- }
- }
- Doco[d] = NULL;
-
- // Any sources or key words?
- if([[self sourceList] count] <= 0)
- { ErrorMsg(errorTitle, "No sources to search."); return nil; }
- if(![self keywords])
- { ErrorMsg(errorTitle, "No key words for search."); return nil; }
-
- // Apportion search limit equally over the sources.
- nsource = [[self sourceList] count];
- limit = ((searchLimit>globalSearchLimit) ? searchLimit : globalSearchLimit)
- / nsource;
-
- // Loop over sources.
- for(s=0; s<nsource; s++)
- {
- // Make sure source is valid and connected.
- source = [[self sourceList] objectAt:s];
- [source setConnected:YES];
- if(![source isConnected])
- {
- ErrorMsg(errorTitle, "Can't connect to source %s.", [source key]);
- continue;
- }
- if(!(database = [source valueForStringKey:":database-name"]))
- {
- ErrorMsg(errorTitle, "Bad source %s.", [source key]);
- continue;
- }
-
- // Lock transaction to prevent conflicts between threads.
- [Wais lockTransaction];
-
- // Create request message.
- request_buffer_length = [source bufferLength];
- if(!generate_search_apdu(request + HEADER_LENGTH,
- &request_buffer_length, [self keywords], database, Doco, limit))
- {
- [Wais unlockTransaction];
- ErrorMsg(errorTitle,
- "Buffer overflow: request too large for source %s.",
- [source key]);
- continue;
- }
-
- // Send request message.
- if(!interpret_message(request, MAX_MESSAGE_LEN - request_buffer_length,
- response, MAX_MESSAGE_LEN, [source connection], false))
- {
- [Wais unlockTransaction];
- ErrorMsg(errorTitle,
- "Warning: no information returned from source %s.",
- [source key]);
- continue;
- }
-
- // Decode search response.
- // Transaction is done; unlock.
- if(!readSearchResponseAPDU(&interp_response, response + HEADER_LENGTH))
- {
- [Wais unlockTransaction];
- ErrorMsg(errorTitle,
- "Source %s returned bad search response.",
- [source key]);
- continue;
- }
- [Wais unlockTransaction];
- if(interp_response
- && (WAISSearchResponse *)interp_response
- ->DatabaseDiagnosticRecords
- && (diag = ((WAISSearchResponse *)interp_response
- ->DatabaseDiagnosticRecords)->Diagnostics)
- )
- for(i=0; diag[i]; i++) if(diag[i]->ADDINFO)
- ErrorMsg(errorTitle, "Search diagnostics: %s, %s",
- diag[i]->DIAG, diag[i]->ADDINFO);
-
- // Build sorted result list with info in DocHeaders of search response.
- nretrieve = interp_response->NumberOfRecordsReturned;
- if(nretrieve==0
- || !interp_response->DatabaseDiagnosticRecords
- || !((WAISSearchResponse*)interp_response
- ->DatabaseDiagnosticRecords)->DocHeaders)
- continue;
- for(d=0; d<nretrieve; d++) if(header=((WAISSearchResponse*)
- interp_response->DatabaseDiagnosticRecords)->DocHeaders[d])
- {
- // Create Wais document.
- //!!! Note that this will mask out any previous doc by this name.
- //!!! Should select type based on prefs.
- if(header->Types) theType = header->Types[0];
- else theType = NULL;
- if(!(doc = [[WaisDocument alloc] initKey:NULL])
- || ![doc setFromSource:source]
- || ![doc setWaisDocIDFromAny:header->DocumentID])
- {
- ErrorMsg(errorTitle, "Can't form document for headline %s.",
- header->Headline);
- continue;
- }
- [doc insertStringKey:":headline" value:header->Headline];
- [doc insertStringKey:":type" value:theType];
- if(![doc setKeyFromInfo])
- {
- ErrorMsg(errorTitle, "Can't form document for headline %s.",
- header->Headline);
- continue;
- }
-
- // Place in result list...keep raw scores descending.
- nresult = [resultList count];
- for(r=0; r<nresult; r++) if(!(score=[[resultList objectAt:r]
- valueForStringKey:":score"]) || header->Score>atol(score))
- break;
- [resultList insertObject:doc at:r];
-
- // Fill in other info fields from header.
- // We ignore header->Source.
- if(header->OriginCity)
- [doc insertStringKey:":origin-city" value:header->OriginCity];
- sprintf(buf, "%d", header->Score);
- [doc insertStringKey:":score" value:buf];
- sprintf(buf, "%ld", header->Lines);
- [doc insertStringKey:":number-of-lines" value:buf];
- sprintf(buf, "%ld", header->DocumentLength);
- [doc insertStringKey:":number-of-bytes" value:buf];
- sprintf(buf, "%ld", header->BestMatch);
- [doc insertStringKey:":best-line" value:buf];
- [doc insertStringKey:":start" value:"-1"];
- [doc insertStringKey:":end" value:"-1"];
- }
- }
-
- // Compute and normalize scores.
- if([resultList count] <= 0)
- {
- ErrorMsg(errorTitle, "Found no documents matching the question.");
- return nil;
- }
- if(![self setScoresFromResults])
- {
- [resultList empty]; [scoreList setNumSlots:0];
- ErrorMsg(errorTitle, "Bad document scores.");
- return nil;
- }
- return resultList;
- }
-
- - resultList
- {
- return resultList;
- }
-
- - (const char *)keywords
- {
- return [self valueForStringKey:":seed-words"];
- }
-
- - setKeywords:(const char *)theText
- {
- [self insertStringKey:":seed-words" value:theText];
- return self;
- }
-
- - addSource:waisSource
- {
- if(![waisSource isKindOf:[WaisSource class]]) return nil;
- return [sourceList addObjectIfAbsent:waisSource];
- }
-
- - removeSource:waisSource
- {
- return [sourceList removeObject:waisSource];
- }
-
- - clearSources
- {
- [sourceList empty];
- return self;
- }
-
- - sourceList
- {
- return sourceList;
- }
-
- - addRelevantDocument:waisDocument
- {
- if(![waisDocument isKindOf:[WaisDocument class]]) return nil;
- return [relevantList addObjectIfAbsent:waisDocument];
- }
-
- - removeRelevantDocument:waisDocument
- {
- return [relevantList removeObject:waisDocument];
- }
-
- - clearRelevantDocuments
- {
- [relevantList empty];
- return self;
- }
-
- - relevantList
- {
- return relevantList;
- }
-
- + setSearchLimit:(int)maxDocs
- {
- if(maxDocs <= 0) return nil;
- globalSearchLimit = maxDocs;
- return self;
- }
-
- + (int)searchLimit
- {
- return globalSearchLimit;
- }
-
- - setSearchLimit:(int)maxDocs
- {
- if(maxDocs <= 0) return nil;
- searchLimit = maxDocs;
- return self;
- }
-
- - (int)searchLimit
- {
- return searchLimit;
- }
-
- - (short)readWaisStruct:(const char *)structName
- forElement:(const char *)elementName
- fromFile:(FILE *)file
- withDecoder:(WaisDecoder)theDecoder
- {
- id subObj, inList, origObj;
- short check_result;
-
- // Check if need additional subobject in a list to capture new data.
- subObj = nil;
- if(0 == strcmp(elementName, ":relevant-documents"))
- { inList = relevantList; subObj = [[WaisDocument alloc] initKey:NULL];}
- else if(0 == strcmp(elementName, ":result-documents"))
- { inList = resultList; subObj = [[WaisDocument alloc] initKey:NULL];}
- else if(0 == strcmp(elementName, ":sources"))
- { inList = sourceList; subObj = [[WaisSource alloc] initKey:NULL];}
- else
- {
- // If not, and done reading full question record, update score list.
- check_result = [super readWaisStruct:structName
- forElement:elementName fromFile:file withDecoder:theDecoder];
- if(check_result == FALSE) return check_result;
- if(![self setScoresFromResults]) return FALSE;
- return check_result;
- }
-
- // Read into subobject.
- if(!subObj || !inList) return FALSE;
- check_result = [subObj readWaisStruct:structName
- forElement:elementName fromFile:file withDecoder:theDecoder];
- if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
- { [subObj free]; return check_result; }
- if(inList == sourceList)
- {
- // Convert source-id's into sources.
- origObj = subObj;
- if(!(subObj = [WaisSource objectForKey:[origObj
- valueForStringKey:":filename"]]))
- {
- ErrorMsg(errorTitle, "Unknown source %s.",
- [origObj valueForStringKey:":filename"]);
- [origObj free]; return TRUE;
- }
- [origObj free];
- }
- [inList addObject:subObj];
- return TRUE;
- }
-
- - (short)writeWaisStruct:(const char *)structName
- forElement:(const char *)elementName
- toFile:(FILE *)file
- withDecoder:(WaisDecoder)theDecoder
- {
- id subObj;
- short check_result;
-
- // Check if need next subobject in a list to extract data from.
- // The listCounter keeps track of where we are in list.
- subObj = nil;
- if(0 == strcmp(elementName, ":relevant-documents"))
- subObj = [relevantList objectAt:listCounter];
- else if(0 == strcmp(elementName, ":result-documents"))
- subObj = [resultList objectAt:listCounter];
- else if(0 == strcmp(elementName, ":sources"))
- subObj = [sourceList objectAt:listCounter];
- else
- {
- listCounter = 0;
- return [super writeWaisStruct:structName
- forElement:elementName toFile:file withDecoder:theDecoder];
- }
-
- // Write from subobject, incrementing or clearing listCounter as needed.
- if(!subObj) { listCounter = 0; return END_OF_STRUCT_OR_LIST; }
- else listCounter++;
- check_result = [subObj writeWaisStruct:structName
- forElement:elementName toFile:file withDecoder:theDecoder];
- if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
- listCounter = 0;
- return check_result;
- }
-
- - writeWaisFile
- {
- // Fill in missing fields.
- [self insertStringKey:":version" value:WAIS_PROTOCOL_VERSION];
- return [super writeWaisFile];
- }
-
-
- @end
-
-
-
-